package com.derbysoft.dswitch.adapter.ihg.common.repository.impl;

import com.derbysoft.dswitch.adapter.ihg.common.domain.ResponseStatus;
import com.derbysoft.dswitch.adapter.ihg.common.domain.TransactionLog;
import com.derbysoft.dswitch.adapter.ihg.common.domain.interceptor.CustomerHibernateLogShardsInterceptor;
import com.derbysoft.dswitch.adapter.ihg.common.repository.TransactionLogService;
import com.derbysoft.dswitch.adapter.ihg.common.utils.DateTimeUtils;
import com.derbysoft.dswitch.adapter.ihg.common.utils.EnvironmentUtils;
import com.derbysoft.dswitch.adapter.ihg.exception.LogTableNotExistException;
import com.derbysoft.dswitch.adapter.ihg.web.action.admin.requestlog.DMXQueryCondition;
import com.derbysoft.dswitch.adapter.ihg.web.action.admin.requestlog.RequestLogCondition;
import com.derbysoft.dswitch.adapter.ihg.web.action.support.Paginater;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.*;
import org.hibernate.exception.SQLGrammarException;
import org.hibernate.impl.CriteriaImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
 * Created by: jason
 * Date: 2012-06-04
 */
@Service("transactionLogService")
public class TransactionLogServiceImpl
        extends CommonService<TransactionLog> implements TransactionLogService {

    private static final Log LOGGER = LogFactory.getLog(TransactionLogServiceImpl.class);
    private static final String TIME_SPAN = "timeSpan";

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public Paginater paginater(DMXQueryCondition condition, Paginater paginater) {
        DetachedCriteria criteria = DetachedCriteria.forClass(TransactionLog.class).addOrder(Order.desc("timestamp"));
        if (condition == null) {
            return super.paginater(criteria, paginater);
        }
        if (condition.getResponseStatus() != null) {
            eq(criteria, "error", condition.getResponseStatus() == ResponseStatus.ERROR);
        }
        if (StringUtils.isNotBlank(condition.getTaskId())) {
            eq(criteria, "taskId", condition.getTaskId());
        }
        if (StringUtils.isNotBlank(condition.getChannelCode())) {
            eq(criteria, "channel", condition.getChannelCode());
        }
        if (StringUtils.isNotBlank(condition.getHotelCode())) {
            eq(criteria, "hotels", condition.getHotelCode());
        }
        if (condition.getMinSpentTime() != null) {
            criteria.add(Restrictions.ge(TIME_SPAN, condition.getMinSpentTime()));
        }
        if (condition.getMaxSpentTime() != null) {
            criteria.add(Restrictions.le(TIME_SPAN, condition.getMaxSpentTime()));
        }

        return fragmentRequired() ? paginateByDate(criteria, getDate(condition), paginater) : super.paginater(criteria, paginater);
    }

    private Date getDate(DMXQueryCondition condition) {
        if (StringUtils.isNotBlank(condition.getStartDate()) && StringUtils.isNotBlank(condition.getEndDate())) {
            return DateTimeUtils.parse(condition.getStartDate());
        } else if (StringUtils.isNotBlank(condition.getStartDate())) {
            return DateTimeUtils.parse(condition.getStartDate());
        }
        return DateTimeUtils.parse(condition.getEndDate());
    }

    @Override
    public Paginater paginater(RequestLogCondition condition, Paginater paginater) {
        DetachedCriteria criteria = DetachedCriteria.forClass(TransactionLog.class).addOrder(Order.desc("timestamp"));
        if (condition == null) {
            return super.paginater(criteria, paginater);
        }
        if (condition.getResponseStatus() != null) {
            eq(criteria, "error", condition.getResponseStatus() == ResponseStatus.ERROR);
        }
        if (StringUtils.isNotBlank(condition.getTaskId())) {
            eq(criteria, "taskId", condition.getTaskId());
        }
        if (StringUtils.isNotBlank(condition.getHotelCode())) {
            eq(criteria, "hotels", condition.getHotelCode());
        }
        if (condition.getMinSpentTime() != null) {
            criteria.add(Restrictions.ge(TIME_SPAN, condition.getMinSpentTime()));
        }
        if (condition.getMaxSpentTime() != null) {
            criteria.add(Restrictions.le(TIME_SPAN, condition.getMaxSpentTime()));
        }
        Date requestDate = StringUtils.isBlank(condition.getDate())
                ? DateTimeUtils.today() : DateTimeUtils.parse(condition.getDate());

        return fragmentRequired() ? paginateByDate(criteria, requestDate, paginater) : super.paginater(criteria, paginater);
    }

    protected Paginater paginateByDate(DetachedCriteria detachedCriteria, Date date, Paginater paginater) {
        Session session = getSession(date);
        try {
            paginater.setTotalCount(count(detachedCriteria, session));
            Criteria criteria = detachedCriteria.getExecutableCriteria(session);
            criteria.setFirstResult(paginater.getFirstResult());
            criteria.setMaxResults(paginater.getMaxResults());
            criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
            List results = criteria.list();
            paginater.setObjects(results);
        } catch (SQLGrammarException e) {
            LOGGER.error(e.getMessage(), e);
            if (e.getCause().getMessage().contains("doesn't exist")) {
                throw new LogTableNotExistException(e.getMessage());
            }
        } finally {
            session.close();
        }
        return paginater;
    }

    private long count(DetachedCriteria detachedCriteria, Session session) {
        Criteria criteria = detachedCriteria.getExecutableCriteria(session);
        Projection originalProjection = ((CriteriaImpl) criteria).getProjection();
        Object count = criteria.setProjection(Projections.countDistinct("id")).uniqueResult();
        detachedCriteria.setProjection(originalProjection);
        return (Long) count;
    }

    private Session getSession(Date date) {
        if (date == null) {
            date = new Date();
        }
        return sessionFactory.openSession(new CustomerHibernateLogShardsInterceptor(date));
    }

    private boolean fragmentRequired() {
        return Boolean.valueOf(EnvironmentUtils.get("avail.log.fragment.required"));
    }

    @Override
    public TransactionLog load(String id, Date date) {
        Session session = getSession(date);
        try {
            return (TransactionLog) session.get(TransactionLog.class, id);
        } catch (SQLGrammarException e) {
            LOGGER.error(e.getMessage(), e);
            if (e.getCause().getMessage().contains("doesn't exist")) {
                throw new LogTableNotExistException(e.getCause().getMessage());
            }
        } finally {
            session.close();
        }
        return null;
    }
}
